/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.cf.code;

import com.android.dx.util.FixedSizeList;

/**
 * List of "line number" entries, which are the contents of
 * {@code LineNumberTable} attributes.
 */
public final class LineNumberList extends FixedSizeList {

	/** {@code non-null;} zero-size instance */
	public static final LineNumberList EMPTY = new LineNumberList(0);

	/**
	 * Returns an instance which is the concatenation of the two given
	 * instances.
	 * 
	 * @param list1
	 *            {@code non-null;} first instance
	 * @param list2
	 *            {@code non-null;} second instance
	 * @return {@code non-null;} combined instance
	 */
	public static LineNumberList concat(LineNumberList list1,
			LineNumberList list2) {
		if (list1 == EMPTY) {
			// easy case
			return list2;
		}

		int sz1 = list1.size();
		int sz2 = list2.size();
		LineNumberList result = new LineNumberList(sz1 + sz2);

		for (int i = 0; i < sz1; i++) {
			result.set(i, list1.get(i));
		}

		for (int i = 0; i < sz2; i++) {
			result.set(sz1 + i, list2.get(i));
		}

		return result;
	}

	/**
	 * Constructs an instance.
	 * 
	 * @param count
	 *            the number of elements to be in the list
	 */
	public LineNumberList(int count) {
		super(count);
	}

	/**
	 * Gets the indicated item.
	 * 
	 * @param n
	 *            {@code >= 0;} which item
	 * @return {@code null-ok;} the indicated item
	 */
	public Item get(int n) {
		return (Item) get0(n);
	}

	/**
	 * Sets the item at the given index.
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which element
	 * @param item
	 *            {@code non-null;} the item
	 */
	public void set(int n, Item item) {
		if (item == null) {
			throw new NullPointerException("item == null");
		}

		set0(n, item);
	}

	/**
	 * Sets the item at the given index.
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which element
	 * @param startPc
	 *            {@code >= 0;} start pc of this item
	 * @param lineNumber
	 *            {@code >= 0;} corresponding line number
	 */
	public void set(int n, int startPc, int lineNumber) {
		set0(n, new Item(startPc, lineNumber));
	}

	/**
	 * Gets the line number associated with the given address.
	 * 
	 * @param pc
	 *            {@code >= 0;} the address to look up
	 * @return {@code >= -1;} the associated line number, or {@code -1} if none
	 *         is known
	 */
	public int pcToLine(int pc) {
		/*
		 * Line number entries don't have to appear in any particular order, so
		 * we have to do a linear search. TODO: If this turns out to be a
		 * bottleneck, consider sorting the list prior to use.
		 */
		int sz = size();
		int bestPc = -1;
		int bestLine = -1;

		for (int i = 0; i < sz; i++) {
			Item one = get(i);
			int onePc = one.getStartPc();
			if ((onePc <= pc) && (onePc > bestPc)) {
				bestPc = onePc;
				bestLine = one.getLineNumber();
				if (bestPc == pc) {
					// We can't do better than this
					break;
				}
			}
		}

		return bestLine;
	}

	/**
	 * Item in a line number table.
	 */
	public static class Item {

		/** {@code >= 0;} start pc of this item */
		private final int startPc;

		/** {@code >= 0;} corresponding line number */
		private final int lineNumber;

		/**
		 * Constructs an instance.
		 * 
		 * @param startPc
		 *            {@code >= 0;} start pc of this item
		 * @param lineNumber
		 *            {@code >= 0;} corresponding line number
		 */
		public Item(int startPc, int lineNumber) {
			if (startPc < 0) {
				throw new IllegalArgumentException("startPc < 0");
			}

			if (lineNumber < 0) {
				throw new IllegalArgumentException("lineNumber < 0");
			}

			this.startPc = startPc;
			this.lineNumber = lineNumber;
		}

		/**
		 * Gets the start pc of this item.
		 * 
		 * @return the start pc
		 */
		public int getStartPc() {
			return startPc;
		}

		/**
		 * Gets the line number of this item.
		 * 
		 * @return the line number
		 */
		public int getLineNumber() {
			return lineNumber;
		}
	}
}
